package cn.sc.summer.redis.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.sc.summer.constant.redis.RedisConstant;
import cn.sc.summer.constant.util.ConvertUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * redis 静态工具类
 *
 * @author a-xin
 */
@Slf4j
public class RedisHelper {

    private static final RedisTemplate<String, Object> redisTemplate = SpringUtil.getBean(RedisConstant.REDIS_BEAN_NAME);

    private static final String INVALID_KEY = "key is null!";

    /**
     * 指定缓存失效时间带单位
     *
     * @param key      键
     * @param time     时间
     * @param timeUnit 时间单位
     * @return true 成功
     */
    public static Boolean expire(String key, long time, TimeUnit timeUnit) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        if (time > 0) {
            redisTemplate.expire(key, time, timeUnit);
        }
        return true;
    }

    /**
     * 指定缓存失效时间无单位默认秒
     *
     * @param key  键
     * @param time 时间(秒)
     * @return true 成功
     */
    public static Boolean expire(String key, long time) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 键值过期时间(秒)
     */
    public static Long getExpire(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key      键 不能为null
     * @param timeUnit 时间单位
     * @return 键值过期时间
     */
    public static Long getExpire(String key, TimeUnit timeUnit) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 重新命名键名称
     *
     * @param oldKey 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public static void rename(String oldKey, String newKey) {
        if (CharSequenceUtil.isBlank(oldKey) || CharSequenceUtil.isBlank(newKey)) {
            log.error(INVALID_KEY);
            return;
        }
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public static Boolean hasKey(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return redisTemplate.hasKey(key);
    }

    /**
     * 删除指定缓存key集合
     *
     * @param keys List集合
     */
    public static Long del(List<String> keys) {
        if (CollUtil.isEmpty(keys)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.delete(keys);
    }

    /**
     * 删除指定缓存key
     *
     * @param key 可以传一个值 或多个
     */
    public static Long del(String... key) {
        if (key == null || key.length == 0) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.delete(Arrays.asList(key));
    }

    /**
     * 删除以指定前缀开头的key,如果存在冒号会自动取代
     *
     * @param prefix 指定前缀
     */
    public static void delKeysByPrefix(String... prefix) {
        for (String pre : prefix) {
            if (StrUtil.isEmpty(pre)) {
                log.error(INVALID_KEY);
                return;
            }
            log.info("==> Deletes redis keys that begin with the specified prefix: {}", pre);
            String delKey = pre.endsWith(":") ? pre.substring(0, prefix.length - 1) : pre;
            Set<String> keys = redisTemplate.keys(delKey + ":*");
            if (CollUtil.isNotEmpty(keys)) {
                redisTemplate.delete(keys);
            }
        }
    }

    /**
     * 删除redis库中所有key值
     */
    public static void delAllKeys() {
        Set<String> keys = redisTemplate.keys("*");
        if (CollUtil.isNotEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 获取以指定前缀开头的key,如果存在冒号会自动取代
     *
     * @param prefix 指定前缀
     */
    public static Set<String> getKeysByPrefix(String prefix) {
        if (StrUtil.isEmpty(prefix)) {
            log.error(INVALID_KEY);
            return new HashSet<>();
        }
        return redisTemplate.keys(prefix.replaceAll(":", "") + ":*");
    }


    /////////////////////////////////string类型/////////////////////////////////

    /**
     * string类型json格式, 无过期时间
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public static Boolean set(String key, Object value) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        redisTemplate.opsForValue().set(key, value);
        return true;
    }

    /**
     * string类型获取值
     *
     * @param key 键
     * @return 值
     */
    public static Object get(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * string类型获取值
     *
     * @param key 键
     * @return 值
     */
    public static <T> T get(String key, Class<T> clazz) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return BeanUtil.toBean(redisTemplate.opsForValue().get(key), clazz);
    }

    /**
     * string类型json格式, 设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public static Boolean set(String key, Object value, long time) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } else {
            set(key, value);
        }
        return true;
    }

    /**
     * string类型json格式, 设置时间,时间单位
     *
     * @param key      键
     * @param value    值
     * @param time     时间
     * @param timeUnit 时间单位
     * @return 是否成功设置
     */
    public static Boolean set(String key, Object value, long time, TimeUnit timeUnit) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, timeUnit);
        } else {
            set(key, value);
        }
        return true;
    }

    /////////////////////////////////hash类型/////////////////////////////////

    /**
     * 获取hash数据中某项属性值
     *
     * @param key  键
     * @param item 项
     * @return 值
     */
    public static Object hGet(String key, String item) {
        if (CharSequenceUtil.isBlank(key)) {
            log.info(INVALID_KEY);
            return null;
        }
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hash数据中所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public static Map<Object, Object> hmGet(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.info(INVALID_KEY);
            return null;
        }
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取hash数据中所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public static <T> T hmGet(String key, Class<T> clazz) {
        if (CharSequenceUtil.isBlank(key)) {
            log.info(INVALID_KEY);
            return null;
        }
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
        return ConvertUtil.converter(entries, clazz);
    }

    /**
     * 获取hash数据中所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public static Object hmGet(String key, Object mapKey) {
        if (CharSequenceUtil.isBlank(key)) {
            log.info(INVALID_KEY);
            return null;
        }
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
        if (CollUtil.isEmpty(entries)) {
            return null;
        }
        if (!entries.containsKey(mapKey)) {
            return null;
        }
        return entries.get(mapKey);
    }

    /**
     * Hash类型存储Map
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public static Boolean hmSet(String key, Map<String, Object> map) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return hmSet(key, map, 0);
    }

    /**
     * Hash类型存储Map,并设置时间
     *
     * @param key      键
     * @param map      对应多个键值
     * @param time     时间(秒)
     * @param timeUnit 时间单位
     * @return true成功 false失败
     */
    public static Boolean hmSet(String key, Map<String, Object> map, long time, TimeUnit timeUnit) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        redisTemplate.opsForHash().putAll(key, map);
        if (time > 0) {
            expire(key, time, timeUnit);
        }
        return true;
    }

    /**
     * Hash类型存储Map,并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public static Boolean hmSet(String key, Map<String, Object> map, long time) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return hmSet(key, map, time, TimeUnit.SECONDS);
    }

    /**
     * 向hash结构中放入数据,如果该项属性不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public static Boolean hSet(String key, String item, Object value) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return hSet(key, item, value, 0);
    }

    /**
     * 向hash结构中放入数据,如果该项属性不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public static Boolean hSet(String key, String item, Object value, long time) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return hSet(key, item, value, time, TimeUnit.SECONDS);
    }

    /**
     * 向hash结构中放入数据,如果该项属性不存在将创建
     *
     * @param key      键
     * @param item     项
     * @param value    值
     * @param time     注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @param timeUnit 时间单位
     * @return true 成功 false失败
     */
    public static Boolean hSet(String key, String item, Object value, long time, TimeUnit timeUnit) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        redisTemplate.opsForHash().put(key, item, value);
        if (time > 0) {
            expire(key, time, timeUnit);
        }
        return true;
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键
     * @param item 项
     */
    public static void hDel(String key, Object... item) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return;
        }
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash结构中是否有该项属性的值
     *
     * @param key  键
     * @param item 项
     * @return true 存在 false不存在
     */
    public static Boolean hHasKey(String key, String item) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return long
     */
    public static Long incr(String key, long delta) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        if (delta <= 0) {
            throw new RuntimeException("The increment factor must be greater than 0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(大于0)
     * @return long
     */
    public static Long decr(String key, long delta) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        if (delta <= 0) {
            throw new RuntimeException("The increment factor must be greater than 0");
        }
        return redisTemplate.opsForValue().decrement(key, delta);
    }


    /////////////////////////////////zset类型/////////////////////////////////

    /**
     * 添加或替换元素
     *
     * @param key    键
     * @param member 成员
     * @param score  得分
     * @return boolean
     */
    public static Boolean zAdd(String key, Object member, double score) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return false;
        }
        return redisTemplate.opsForZSet().add(key, member, score);
    }

    /**
     * 获取有序集合中指定范围内的成员集合（按分数从低到高排序）
     *
     * @param key   键
     * @param start 开始位置
     * @param end   停止位置
     * @return Set结果集
     */
    public static Set<ZSetOperations.TypedTuple<Object>> zGetAES(String key, int start, int end) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 获取有序集合中所有成员集合（按分数从低到高排序）
     *
     * @param key 键
     * @return Set结果集
     */
    public static Set<ZSetOperations.TypedTuple<Object>> zGetAllAES(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return zGetAES(key, 0, -1);
    }

    /**
     * 获取有序集合中指定范围内的成员集合（按分数从高到低排序）
     *
     * @param key   键
     * @param start 开始位置
     * @param end   停止位置
     * @return Set结果集
     */
    public static Set<ZSetOperations.TypedTuple<Object>> zGetDES(String key, int start, int end) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
    }

    /**
     * 获取有序集合中所有成员集合（按分数从高到低排序）
     *
     * @param key 键
     * @return Set结果集
     */
    public static Set<ZSetOperations.TypedTuple<Object>> zGetAllDES(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return zGetDES(key, 0, -1);
    }

    /**
     * 获取有序集合长度
     *
     * @param key 键
     * @return Set长度
     */
    public static Integer zGetSize(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0;
        }
        if (hasKey(key)) {
            return zGetDES(key, 0, -1).size();
        } else {
            return 0;
        }
    }

    /**
     * 获取有序集合中的成员数量
     *
     * @param key 键
     * @return 成员数量
     */
    public static Long zMemberCount(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 获取有序集合中指定成员的分数
     *
     * @param key    键
     * @param member 成员
     * @return 分数
     */
    public static Double zGetScore(String key, Object member) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0D;
        }
        return redisTemplate.opsForZSet().score(key, member);
    }

    /**
     * 获取有序集合中最大分数值
     *
     * @param key 键
     * @return 分数
     */
    public static Object zGetScoreMax(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        if (!hasKey(key)) {
            throw new RuntimeException("The key " + key + " not exits!");
        }
        return new ArrayList<>(zGetAllDES(key)).get(0).getValue();
    }

    /**
     * 获取有序集合中最小分数值
     *
     * @param key 键
     * @return 分数
     */
    public static Object zGetScoreMin(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        if (!hasKey(key)) {
            throw new RuntimeException("The key " + key + " not exits!");
        }
        return new ArrayList<>(zGetAllAES(key)).get(0).getValue();
    }

    /**
     * 从有序集合中移除指定的成员
     *
     * @param key    键
     * @param values 成员
     * @return 移除数量
     */
    public static Long zRemoveMember(String key, Object... values) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * 删除并返回分数最大值
     *
     * @param key 键
     * @return 移除数量
     */
    public static ZSetOperations.TypedTuple<Object> zRemoveScoreMax(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return redisTemplate.opsForZSet().popMax(key);
    }

    /**
     * 删除并返回分数最小值
     *
     * @param key 键
     * @return 移除数量
     */
    public static ZSetOperations.TypedTuple<Object> zRemoveScoreMin(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return null;
        }
        return redisTemplate.opsForZSet().popMin(key);
    }

    /**
     * 将指定成员的分数增加指定数值
     *
     * @param key    键
     * @param member 成员
     * @param score  分数
     * @return 新的分数
     */
    public static Double zIncrementScore(String key, Object member, Double score) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0D;
        }
        return redisTemplate.opsForZSet().incrementScore(key, member, score);
    }

    /**
     * 获取指定成员在有序集合中的排名（按分数从低到高排序）
     *
     * @param key    键
     * @param member 成员
     * @return 排名
     */
    public static Long zGetSortAES(String key, Object member) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForZSet().rank(key, member);
    }

    /**
     * 获取指定成员在有序集合中的排名（按分数从高到低排序）
     *
     * @param key    键
     * @param member 成员
     * @return 排名
     */
    public static Long zGetSortDES(String key, Object member) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForZSet().reverseRank(key, member);
    }


    /////////////////////////////////list类型/////////////////////////////////

    /**
     * 从集合左边插入值
     *
     * @param key   键
     * @param value 值
     * @return 添加元素后的新长度
     */
    public static Long lAdd(String key, Object value) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 批量添加list值
     *
     * @param key    键
     * @param values list值
     * @param <T>    对象
     * @return 添加元素后的新长度
     */
    public static <T> Long lAddAll(String key, Collection<T> values) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().leftPushAll(key, values.stream().distinct().toArray());
    }

    /**
     * 批量添加list值
     *
     * @param key    键
     * @param values list值
     * @return 添加元素后的新长度
     */
    public static Long lAddAll(String key, Object... values) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    /**
     * 截取一段值保留，其余的删除
     *
     * @param key   键
     * @param start 开始下标
     * @param end   结束下标
     */
    public static void lTrim(String key, Long start, Long end) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return;
        }
        redisTemplate.opsForList().trim(key, start, end);
    }

    /**
     * 移除key中最后插入的值
     *
     * @param key 键
     * @return 移除的值
     */
    public static Object lRemoveRecent(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移除key中最早插入的值
     *
     * @param key 键
     * @return 移除的值
     */
    public static Object lRemoveEarly(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 移除key中指定值
     *
     * @param key   键
     * @param count 第几次出现，count> 0：删除等于从左到右移动的值的第一个元素；count< 0：删除等于从右到左移动的值的第一个元素；count = 0：删除等于value的所有元素。
     * @param value 值
     * @return 移除的值
     */
    public static Long lRemove(String key, Long count, Object value) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().remove(key, count, value);
    }

    /**
     * 移除key中指定值
     *
     * @param key   键
     * @param value 值
     * @return 移除的值
     */
    public static Long lRemove(String key, Object value) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().remove(key, 0, value);
    }

    /**
     * 获取键的长度
     *
     * @param key 键
     * @return 长度
     */
    public static Long lGetSize(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return 0L;
        }
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 获取指定下标的值，end为 -1 则获取所有值
     *
     * @param key 键
     * @return 值
     */
    public static List<Object> lGetRange(String key, long start, long end) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return new ArrayList<>();
        }
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取指定下标的值，end为 -1 则获取所有值
     *
     * @param key 键
     * @return 值
     */
    public static <T> List<T> lGetRange(String key, long start, long end, Class<T> clsType) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return new ArrayList<>();
        }
        List<Object> range = redisTemplate.opsForList().range(key, start, end);
        return ConvertUtil.convertToList(range, clsType);
    }

    /**
     * 获取所有的值
     *
     * @param key 键
     * @return 值
     */
    public static List<Object> lGetAll(String key) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return new ArrayList<>();
        }
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 获取所有的值
     *
     * @param key 键
     * @return 值
     */
    public static <T> List<T> lGetAll(String key, Class<T> clsType) {
        if (CharSequenceUtil.isBlank(key)) {
            log.error(INVALID_KEY);
            return new ArrayList<>();
        }
        List<Object> range = redisTemplate.opsForList().range(key, 0, -1);
        return ConvertUtil.convertToList(range, clsType);
    }

}
